import xml.etree.ElementTree as ET
import json
import os

# 定义一个函数来转换XML节点为字典
def xml_to_dict(node):
    node_dict = {}
    # 处理节点的属性
    if node.attrib:
        node_dict.update(node.attrib)
    # 处理子节点
    for child in node:
        child_dict = xml_to_dict(child)
        if child.tag not in node_dict:
            node_dict[child.tag] = child_dict
        else:
            if not isinstance(node_dict[child.tag], list):
                node_dict[child.tag] = [node_dict[child.tag]]
            node_dict[child.tag].append(child_dict)
    # 处理节点的文本
    if node.text and node.text.strip():
        node_dict['text'] = node.text.strip()
    return node_dict

# 解析XML文件
def opcxml2opcjson(path):
    tree = ET.parse(path)
    root = tree.getroot()

    # 将XML根节点转换为字典
    xml_dict = xml_to_dict(root)

    # 将字典转换为JSON格式
    json_data = json.dumps(xml_dict, indent=2, ensure_ascii=False)

    # 将JSON数据写入文件
    # with open(path.rsplit('.', 1)[0] + '.json', 'w', encoding='utf-8') as json_file:
    #     json_file.write(json_data)

    print('OPC-XML文件'+path+'已成功转换为OPC-JSON格式。')
    return json_data

def opc_json2unified_json(path, json_data):
    def post_process_object(obj, level):
        if isinstance(obj, dict):
            new_obj = {}
            for key, value in obj.items():
                # 冗余文本删除
                new_key = key.split('}')[-1] if '}' in key else key
                
                # 冗余数据对象删除
                if new_key in ['NamespaceUris', 'LastModified', 'Models', 'Extensions', 
                               'UAObjectType',"UserAccessLevel"]:
                    continue
                # 递归处理子对象
                new_obj[new_key] = post_process_object(value, level+1)
            
            # 嵌套层次缩减
            if len(new_obj) == 1 and 'text' in new_obj:
                return new_obj['text']
            # 转xml需要保留object嵌套list的结构
            # if len(new_obj) == 1 and isinstance(list(new_obj.values())[0], list):
            #     return list(new_obj.values())[0]
            return new_obj
        
        elif isinstance(obj, list):
            return [post_process_object(item, level+1) for item in obj]

        return obj

    def alias_process_object(obj, level):
        nonlocal aliases
        if isinstance(obj, dict):
            new_obj = {}

            for key, value in obj.items():  
                # 别名替换
                if key == 'Aliases':
                    aliases = {**aliases, **{alias['text']: alias['Alias'] for alias in value.get("Alias", [])}}
                
                # 递归处理子对象
                temp_obj = alias_process_object(value, level+1)
                if temp_obj:  # 判断返回的对象是否为{}
                    new_obj[key] = temp_obj
            
            return new_obj
        
        elif isinstance(obj, list):
            return [temp_obj for item in obj if (temp_obj := alias_process_object(item, level + 1))]  # 只有返回的对象不为{}才加入数组
        
        elif isinstance(obj, str):
            if obj in aliases:
                obj = aliases[obj]

        return obj
    
    def spot_process_object(obj, level):
        if isinstance(obj, dict):
            new_obj = {}
            nonlocal deviceInfo, service, characteristics
            flag = False
            # reference识别层，发现标注的关键服务、特征
            if "References" in obj:
                references = obj["References"].get("Reference", [])
                if isinstance(references, list):
                    obj["References"]["Reference"] = [
                        reference for reference in references
                        if reference.get("ReferenceType") == "HasTypeDefinition" 
                        and reference.get("text") in ["ServiceType", "CharacteristicType", "DeviceInfoType"]
                    ]
                else:
                    obj["References"]["Reference"] = [
                        references
                    ] if references.get("ReferenceType") == "HasTypeDefinition" and references.get("text") in ["ServiceType", "CharacteristicType", "DeviceInfoType"] else []

                for reference in obj["References"].get("Reference", []):
                    if reference.get("ReferenceType") == "HasTypeDefinition":
                        if reference.get("text") == "ServiceType":
                            service.append(obj)
                            flag = True
                        elif reference.get("text") == "CharacteristicType":
                            characteristics.append(obj)
                            flag = True
                        elif reference.get("text") == "DeviceInfoType":
                            deviceInfo.append(obj)
                            flag = True
            # 非reference识别层
            else: flag = True

            if not flag:
                return new_obj

            for key, value in obj.items():                  
                temp_obj = spot_process_object(value, level+1)

                if temp_obj:  # 判断返回的对象是否为{}
                    new_obj[key] = temp_obj
            
            return new_obj
        
        elif isinstance(obj, list):
            return [temp_obj for item in obj if (temp_obj := spot_process_object(item, level + 1))]  # 只有返回的对象不为{}才加入数组

        return obj
    
    def graph_process_object(data, level):
        flop_obj = {}
        flop_obj["aliases"] = aliases
        flop_obj["deviceInfo"] = deviceInfo if deviceInfo else [{"NodeId": "i=85"}]
        flop_obj["services"] = service if service else []
        flop_obj["characteristics"] = characteristics if characteristics else []

        # 创建一个字典来存储所有节点，以NodeId为键
        node_map = {}
        # 遍历所有类别的节点，并存入node_map
        for category in ['deviceInfo', 'services', 'characteristics']:
            if category in flop_obj:
                for node in flop_obj[category]:
                    # 节点适配物模型处理
                    if(category == 'deviceInfo'):
                        node["prodId"] = (node.get("BrowseName", "opc defalut prodId") + ';' + node.get("NodeId", ""))
                        node["deviceName"] = node.get("DisplayName", "opc default deviceName")
                        node["deviceTypeName"] = node.get("BrowseName", "opc defalut deviceTypeName").split(':')[-1]
                        node["deviceTypeId"] = ' opc default TypeId;' + node.get("NodeId", "")
                        node["manufacturerName"] = 'opc default ManufacturerName'
                        node["manufacturerId"] = 'opc default ManufacturerId;' + node.get("NodeId", "")
                        node["deviceModel"] = 'opc default Model'
                        node["deviceProtocol"] = 'OPC-UA'
                        node["description"] = 'opc default description'
                        if "BrowseName" in node: del node["BrowseName"]
                        if "DisplayName" in node: del node["DisplayName"]
                    elif(category == 'services'):
                        node["serviceName"] = node.get("BrowseName", "opc default serviceName").split(':')[-1]
                        node["serviceType"] = node.get("BrowseName", "opc default serviceType").split(':')[-1] + ';' + node.get("NodeId", "")
                        node["serviceId"] = node.get("DisplayName", "opc default serviceId")
                        if "BrowseName" in node: del node["BrowseName"]
                        if "DisplayName" in node: del node["DisplayName"]
                    elif(category == 'characteristics'):
                        node["characteristicName"] = node.get("BrowseName", "opc default characteristicName").split(':')[-1]
                        node["characteristicType"] = node.get("DataType", "i=58")
                        access_level = node.get("AccessLevel", "0")
                        if access_level == "1":
                            node["method"] = "R"
                        elif access_level == "2":
                            node["method"] = "W"
                        elif access_level == "3":
                            node["method"] = "RW"
                        else:
                            node["method"] = "RW"
                        node["description"] = 'opc default description'
                        # 从"Value": { "UInt32": "0"} 中提取值，如果没有则根据类型赋默认值
                        if "Value" in node:
                            node["value"] = node["Value"].get(node["characteristicType"], "undefined value") 
                        else:
                            node["value"] = 1 if "Int" in node["characteristicType"] else (0.1 if "Float" in node["characteristicType"] else "default string value" if "String" in node["characteristicType"] else "undefined value")
                        
                        if "BrowseName" in node: del node["BrowseName"]
                        if "DisplayName" in node: del node["DisplayName"]
                        if "AccessLevel" in node: del node["AccessLevel"]
                        if "DataType" in node: del node["DataType"]
                        if "Value" in node: del node["Value"]
                    # 存入map
                    node_id = node['NodeId']
                    node_map[node_id] = node
        # 再次遍历所有节点，将子节点关联到父节点
        for category in ['deviceInfo', 'services', 'characteristics']:
            if category in flop_obj:
                for node in flop_obj[category]:
                    parent_id = node.get('ParentNodeId')
                    if parent_id and parent_id in node_map:
                        parent_node = node_map[parent_id]
                        if category not in parent_node:
                            parent_node[category] = []
                        parent_node[category].append(node)
        
        # 移除原始平铺结构，只保留根节点（ParentNodeId为i=85的设备节点）
        root_nodes = {}
        root_nodes["aliases"] = flop_obj["aliases"]
        for node in flop_obj.get('deviceInfo', []):
            root_nodes['deviceInfo'] = node        
        return root_nodes

    aliases = {
    }
    deviceInfo = []
    service = []
    characteristics = []

    # 预处理JSON数据，去除杂乱信息和无用信息
    processed_data = post_process_object(json.loads(json_data),0)
    # 别名alias替换
    processed_data = alias_process_object(processed_data, 0)
    # 发现服务和特征
    processed_data = spot_process_object(processed_data, 0)
    # 将图结构化为二维化服务和特征
    processed_data = graph_process_object(processed_data, 0)
    
    # 将处理后的JSON数据写入文件
    with open(path.rsplit('.', 1)[0] + '-unified.json', 'w', encoding='utf-8') as json_file:
        json_file.write(json.dumps(processed_data, indent=2, ensure_ascii=False))

    print('OPC-json文件'+path+'已成功转换为OH-JSON格式。')

def convertPipeLine(current_dir, path):
    json_data = opcxml2opcjson(os.path.join(current_dir, 'opcua_define', path))
    opc_json2unified_json(os.path.join(current_dir, 'unified_define', path), json_data)

current_dir = os.path.dirname(__file__)
convertPipeLine(current_dir, "OpenharmonyDna.xml")
convertPipeLine(current_dir, "example.xml")
convertPipeLine(current_dir, "DNA.xml")
convertPipeLine(current_dir, "GibhDemo2.xml")